namespace Defiant.SqlServer.PowerShellScriptTask
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Management.Automation;
    using System.Management.Automation.Runspaces;
    using System.Xml;

    using Microsoft.SqlServer.Dts.Runtime;

    [DtsTask(DisplayName = "PowerShell Script Task",
        UITypeName =
            "Defiant.SqlServer.PowerShellScriptTask.PowerShellTaskUI, Defiant.SqlServer.PowerShellScriptTask, Version=1.0.0.0, Culture=neutral, PublicKeyToken=6a067098dbb447d8"
        , IconResource = "Defiant.SqlServer.PowerShellScriptTask.msh_main.ico",
        TaskContact = "PowerShell Script Task; http://powershellscripttask.codeplex.com/",
        RequiredProductLevel = DTSProductLevel.None)]
    public class PowerShellScriptTask : Task, IDTSComponentPersist
    {
        #region Constructor

        public PowerShellScriptTask()
        {
            this.DebugOnExecute = false;
            this.Script = string.Empty;
            this.ReadWriteVariables = new List<string>();
            this.ReadOnlyVariables = new List<string>();
        }

        #endregion

        public string Script { get; set; }

        internal List<string> ReadOnlyVariables { get; set; }

        internal List<string> ReadWriteVariables { get; set; } 

#if DEBUG
		 public bool DebugOnExecute { get; set; } 
#endif

        public override int Version
        {
            get
            {
                //Assembly asm = Assembly.GetExecutingAssembly();
                //object[] attrs = asm.GetCustomAttributes(typeof(AssemblyVersionAttribute,true));
                //AssemblyVersionAttribute attr = (AssemblyVersionAttribute)attr[0];
                return base.Version;
            }
        }

        public override DTSExecResult Validate(Connections connections, VariableDispenser variableDispenser, IDTSComponentEvents componentEvents, IDTSLogging log)
        {
            try
            {
                if (this.Script.Length == 0)
                {
                    componentEvents.FireError(0, Constants.COMPONENT, "The Script property must be configured.", string.Empty, 0);
                    return DTSExecResult.Failure;
                }
                return DTSExecResult.Success;
            }
            catch (Exception ex)
            {
                componentEvents.FireError(0, Constants.COMPONENT, ex.Message, string.Empty, 0);
                return DTSExecResult.Failure;
            }
        }

        public override DTSExecResult Execute(Connections connections, VariableDispenser variableDispenser, IDTSComponentEvents componentEvents, IDTSLogging log, object transaction)
        {
#if DEBUG
 
            if (this.DebugOnExecute)
            {
                if (System.Diagnostics.Debugger.IsAttached == false)
                {
                    System.Diagnostics.Debugger.Launch();
                }
            }
#endif
            try
            {
                InitialSessionState state = CreateSessionStateWithVariables(variableDispenser, componentEvents);

                // Create the runspace
                ScriptHost host = new ScriptHost(componentEvents);
                using (Runspace rs = RunspaceFactory.CreateRunspace(host, state))
                {
                    rs.Open();

                    // Create the command to execute the script
                    Pipeline pipeline = rs.CreatePipeline();
                    Command cmd = new Command(this.Script, true);
                    pipeline.Commands.Add(cmd);
                    Collection<PSObject> results = pipeline.Invoke();

                    // Update the variable in SSIS from PS
                    this.UpdateSsisVariables(variableDispenser, rs);
                }

                return DTSExecResult.Success;
            }
            catch (Exception ex)
            {
                componentEvents.FireError(0, Constants.COMPONENT, ex.Message, string.Empty, 0);
                return DTSExecResult.Failure;
            }
        }

        public void SaveToXML(XmlDocument doc, IDTSInfoEvents infoEvents)
        {
            XmlElement settingsRoot = doc.CreateElement(null, "PowershellScript", null);

            XmlElement scriptElement = doc.CreateElement(null, "Script", null);
            scriptElement.InnerText = this.Script;
            settingsRoot.AppendChild(scriptElement);

            XmlElement variablesElement = doc.CreateElement(null, "Variables", null);
            foreach (string variableName in this.ReadOnlyVariables)
            {
                XmlElement variableElement = doc.CreateElement(null, "Variable", null);
                variableElement.SetAttribute("ReadOnly", "true");
                variableElement.InnerText = variableName;
                variablesElement.AppendChild(variableElement);
            }
            foreach (string variableName in this.ReadWriteVariables)
            {
                XmlElement variableElement = doc.CreateElement(null, "Variable", null);
                variableElement.SetAttribute("ReadOnly", "false");
                variableElement.InnerText = variableName;
                variablesElement.AppendChild(variableElement);
            }
            settingsRoot.AppendChild(variablesElement);

#if DEBUG
            XmlElement debugElement = doc.CreateElement(null, "DebugOnExecute", null);
            debugElement.InnerText = this.DebugOnExecute.ToString();
            settingsRoot.AppendChild(debugElement);
#endif

            doc.AppendChild(settingsRoot);
        }

        public void LoadFromXML(XmlElement node, IDTSInfoEvents infoEvents)
        {
            foreach (XmlElement element in node.ChildNodes)
            {
                if (element.Name == "Script")
                {
                    this.Script = element.InnerText;
                }
                if (element.Name == "Variables")
                {
                    this.ReadOnlyVariables = new List<string>();
                    this.ReadWriteVariables = new List<string>();
                    foreach (XmlElement variableElement in element.ChildNodes)
                    {
                        bool readOnly = Convert.ToBoolean(variableElement.GetAttribute("ReadOnly"));
                        if (readOnly)
                        {
                            this.ReadOnlyVariables.Add(variableElement.InnerText);
                        }
                        else
                        {
                            this.ReadWriteVariables.Add(variableElement.InnerText);
                        }
                    }
                }
                if (element.Name == "DebugOnExecute")
                {
                    this.DebugOnExecute = Convert.ToBoolean(element.InnerText);
                }
            }
        }

        private InitialSessionState CreateSessionStateWithVariables(VariableDispenser variableDispenser, IDTSComponentEvents componentEvents)
        {
            InitialSessionState state = InitialSessionState.CreateDefault();

            foreach (var readOnlyVariable in ReadOnlyVariables)
            {
                variableDispenser.LockForRead(readOnlyVariable);
            }
            foreach (var readWriteVariable in ReadWriteVariables)
            {
                variableDispenser.LockForWrite(readWriteVariable);
            }

            Variables vars = null;
            variableDispenser.GetVariables(ref vars);
            for (int count = 0; count < vars.Count; count++)
            {
                Variable variable = vars[count];
                string variableName = variable.QualifiedName.Replace("::", "_");

                ScopedItemOptions scope = variable.ReadOnly ? ScopedItemOptions.ReadOnly : ScopedItemOptions.AllScope;
                SessionStateVariableEntry variableEntry = new SessionStateVariableEntry(
                    variableName, variable.Value, "SSIS Variable", scope);
                state.Variables.Add(variableEntry);
            }
            vars.Unlock();
            variableDispenser.Reset();

            // Add the logger to PowerShell
            SSISHelper helper = new SSISHelper(componentEvents);
            state.Variables.Add(new SessionStateVariableEntry("ssis", helper, "SSIS Helper", ScopedItemOptions.AllScope));

            return state;
        }

        private void UpdateSsisVariables(VariableDispenser variableDispenser, Runspace runspace)
        {
            Variables vars = null;
            foreach (var readWriteVariable in this.ReadWriteVariables)
            {
                variableDispenser.LockOneForWrite(readWriteVariable, ref vars);
            }

            for (int count = 0; count < vars.Count; count++)
            {
                Variable variable = vars[count];

                if (variable.ReadOnly == false)
                {
                    string variableName = variable.QualifiedName.Replace("::", "_");
                    object valueFromPS = runspace.SessionStateProxy.GetVariable(variableName);
                    variable.Value = this.ConvertDataType(valueFromPS.ToString(), variable.DataType);
                }
            }

            vars.Unlock();
            variableDispenser.Reset();
        }

        private object ConvertDataType(string value, TypeCode type)
        {
            switch (type)
            {
                case TypeCode.Boolean:
                    return bool.Parse(value);
                case TypeCode.Byte:
                    return byte.Parse(value);
                case TypeCode.Char:
                    return char.Parse(value);
                case TypeCode.DBNull:
                    return null;
                case TypeCode.DateTime:
                    return DateTime.Parse(value);
                case TypeCode.Decimal:
                    return decimal.Parse(value);
                case TypeCode.Double:
                    return double.Parse(value);
                case TypeCode.Empty:
                    return null;
                case TypeCode.Int16:
                    return Int16.Parse(value);
                case TypeCode.Int32:
                    return Int32.Parse(value);
                case TypeCode.Int64:
                    return Int64.Parse(value);
                case TypeCode.Object:
                    return value;
                case TypeCode.SByte:
                    return SByte.Parse(value);
                case TypeCode.Single:
                    return Single.Parse(value);
                case TypeCode.String:
                    return value;
                case TypeCode.UInt16:
                    return UInt16.Parse(value);
                case TypeCode.UInt32:
                    return UInt32.Parse(value);
                case TypeCode.UInt64:
                    return UInt64.Parse(value);
                default:
                    return value;
            }
        }
    }
}